<?php

declare(strict_types=1);

namespace App\Traits\Request;



use Hyperf\Collection\Arr;
use Hyperf\Context\Context;
use Hyperf\HttpServer\Router\Dispatched;

trait FormRequest
{
    /**
     * 真实IP
     * @var string
     */
    protected string $realIP;

    /**
     * 前端代理服务器IP
     * @var array
     */
    protected array $proxyServerIp = [
        '127.0.0.1'
    ];

    /**
     * 前端代理服务器真实IP头
     * @var array
     */
    protected array $proxyServerIpHeader = [
        'x-real-ip'
    ];

    /**
     * 获取请求方法
     * @return mixed
     */
    public function getAction():string
    {
        return $this->getAttribute(Dispatched::class)->handler->callback[1];
    }

    /**
     * 获取路径
     * @return string
     */
    public function getPath():string
    {
        return $this->getAttribute(Dispatched::class)->handler->callback[0];
    }

    /**
     * 参数白名单
     * @param array $only
     * @return array
     */
    public function only(array $name, $filter='')
    {
        $data = $this->all();
        $item = [];
        foreach ($name as $key => $val) {
            if (is_int($key)) {
                $default = null;
                $key = $val;
                $oldKey = $key;
                if (strpos($key, '/')) {
                    [$oldKey] = explode('/', $key);
                }
                if (!key_exists($oldKey, $data)) {
                    continue;
                }
            } else {
                $default = $val;
            }


            $keyName = $key;
            if (strpos($key, '/')) {
                [$keyName] = explode('/', $key);
            }
            if (false === Arr::has($data, $keyName) && null === $default) {
                continue;
            }
            $item[$keyName] = $this->getInput($data, $key, $default, $filter);
        }

        return $item;
    }

    /**
     * 获取并过滤所有参数
     * @param string $filter
     * @return array
     */
    public function getAll($filter='')
    {
        $data = $this->all();

        $item = [];
        foreach ($data as $key => $value) {
            $item[$key] = $this->getInput($data, $key, null, $filter);
        }

        return $item;
    }

    /**
     * 获取变量 支持过滤和默认值
     * @param array $data
     * @param string $name
     * @param null $default
     * @param string $filter
     * @return array|mixed|null
     */
    protected function getInput(array $data = [], mixed $name = '', $default = null, $filter = '')
    {
        if (false === $name) {
            // 获取原始数据
            return $data;
        }

        $name = (string) $name;
        if ('' != $name) {
            // 解析name
            if (strpos($name, '/')) {
                [$name, $type] = explode('/', $name);
            }

            $data = $this->getData($data, $name);

            if (is_null($data)) {
                return $default;
            }

            if (is_object($data)) {
                return $data;
            }
        }

        $data = $this->filterData($data, $filter, $name, $default);

        if (isset($type) && $data !== $default) {
            // 强制类型转换
            $this->typeCast($data, $type);
        }

        return $data;
    }

    /**
     * 强制类型转换
     * @access protected
     * @param  mixed  $data
     * @param  string $type
     * @return mixed
     */
    protected function typeCast(&$data, string $type)
    {
        switch (strtolower($type)) {
            // 数组
            case 'a':
                $data = (array) $data;
                break;
            // 数字
            case 'd':
                $data = (int) $data;
                break;
            // 浮点
            case 'f':
                $data = (float) $data;
                break;
            // 布尔
            case 'b':
                $data = (boolean) $data;
                break;
            // 字符串
            case 's':
                if (is_scalar($data)) {
                    $data = (string) $data;
                } else {
                    throw new \InvalidArgumentException('variable type error：' . gettype($data));
                }
                break;
        }
    }

    /**
     * 获取数据
     * @access protected
     * @param  array  $data 数据源
     * @param  string $name 字段名
     * @param  mixed  $default 默认值
     * @return mixed
     */
    protected function getData(array $data, string $name, $default = null)
    {
        foreach (explode('.', $name) as $val) {
            if (isset($data[$val])) {
                $data = $data[$val];
            } else {
                return $default;
            }
        }

        return $data;
    }

    /**
     * 过滤器
     * @param $data
     * @param $filter
     * @param $name
     * @param $default
     * @return mixed
     */
    protected function filterData($data, $filter, $name, $default)
    {
        // 解析过滤器
        $filter = $this->getFilter($filter, $default);

        if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
        } else {
            $this->filterValue($data, $name, $filter);
        }

        return $data;
    }

    /**
     * 获取
     * @param $filter
     * @param $default
     * @return array
     */
    protected function getFilter($filter, $default): array
    {
        if (is_null($filter)) {
            $filter = [];
        } else {
            $filter = $filter ?: $this->filter;
            if (is_string($filter) && false === strpos($filter, '/')) {
                $filter = explode(',', $filter);
            } else {
                $filter = (array) $filter;
            }
        }

        $filter[] = $default;

        return $filter;
    }

    /**
     * 递归过滤给定的值
     * @access public
     * @param  mixed $value 键值
     * @param  mixed $key 键名
     * @param  array $filters 过滤方法+默认值
     * @return mixed
     */
    public function filterValue(&$value, $key, $filters)
    {
        $default = array_pop($filters);

        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 调用函数或者方法过滤
                if (is_null($value)) {
                    continue;
                }

                $value = call_user_func($filter, $value);
            } elseif (is_scalar($value)) {
                if (is_string($filter) && false !== strpos($filter, '/')) {
                    // 正则过滤
                    if (!preg_match($filter, $value)) {
                        // 匹配不成功返回默认值
                        $value = $default;
                        break;
                    }
                } elseif (!empty($filter)) {
                    // filter函数不存在时, 则使用filter_var进行过滤
                    // filter为非整形值时, 调用filter_id取得过滤id
                    $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
                    if (false === $value) {
                        $value = $default;
                        break;
                    }
                }
            }
        }

        return $value;
    }

    /**
     * 获取请求类型
     * @return string
     */
    public function scheme():string
    {
        return $this->checkRemoteIP()?
            current($this->getHeader('X-Forwarded-Proto', 'http')):
            'http';
    }

    /**
     * 获取域名
     * @return string
     */
    public function domain():string
    {
        $http = $this->scheme();
        $port = $this->getRequest()->getUri()->getPort();
        $portString = $port?":{$port}":'';
        $host = $this->getHost();
        return "{$http}://{$host}{$portString}";
    }

    /**
     * 获取无前缀,端口的域名
     * @return string
     */
    public function getHost():string
    {
        return $this->getRequest()->getUri()->getHost();
    }

    /**
     * 获取当前应用名称
     * @return string
     */
    public function appName():string
    {
        $path = $this->path();
        preg_match('/^(.*?)\//', $path, $app);

        return $app[1]??'';
    }

    /**
     * 获取IP
     * @return string
     */
    public function ip():string
    {
        if (Context::has('real_ip')) return Context::get('real_ip');

        $realIP = $this->server('remote_addr', '');

        // 如果指定了前端代理服务器IP以及其会发送的IP头
        // 则尝试获取前端代理服务器发送过来的真实IP
        $proxyIpHeader = $this->proxyServerIpHeader;

        if (count($proxyIpHeader) > 0) {
            // 从指定的HTTP头中依次尝试获取IP地址
            // 直到获取到一个合法的IP地址
            foreach ($proxyIpHeader as $header) {
                $tempIP = current($this->getHeader(strtolower($header)));

                if (empty($tempIP)) {
                    continue;
                }

                if (!$this->isValidIP($tempIP)) {
                    $tempIP = null;
                } else {
                    break;
                }
            }

            // tempIP不为空，说明获取到了一个IP地址
            // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一
            // 如果是的话说明该 IP头 是由前端代理服务器设置的
            // 否则则是伪装的
            if (!empty($tempIP) && $this->checkRemoteIP($realIP)) {
                $realIP = $tempIP;
            }
        }

        if (!$this->isValidIP($realIP)) {
            $realIP = '0.0.0.0';
        }

        Context::set('real_ip', $realIP);
        return $realIP;
    }

    /**
     * 检查前端代理IP地址是否合法
     * @param $realIP
     * @return bool
     */
    public function checkRemoteIP(string $realIP=null): bool
    {
        $proxyIp = $this->proxyServerIp;
        /**
         * 如果没有指定前端代理服务器的IP地址，则不验证
         */
        if (count($proxyIp) <= 0) {
            return true;
        }

        if (!$realIP) {
            $realIP = $this->server('remote_addr', '');
        }
        $realIPBin = $this->ip2bin($realIP);
        foreach ($proxyIp as $ip) {
            $serverIPElements = explode('/', $ip);
            $serverIP         = $serverIPElements[0];
            $serverIPPrefix   = $serverIPElements[1] ?? 128;
            $serverIPBin      = $this->ip2bin($serverIP);

            // IP类型不符
            if (strlen($realIPBin) !== strlen($serverIPBin)) {
                continue;
            }

            if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * 将IP地址转换为二进制字符串
     *
     * @param string $ip
     *
     * @return string
     */
    public function ip2bin(string $ip): string
    {
        if ($this->isValidIP($ip, 'ipv6')) {
            $IPHex = str_split(bin2hex(inet_pton($ip)), 4);
            foreach ($IPHex as $key => $value) {
                $IPHex[$key] = intval($value, 16);
            }
            $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex);
        } else {
            $IPHex = str_split(bin2hex(inet_pton($ip)), 2);
            foreach ($IPHex as $key => $value) {
                $IPHex[$key] = intval($value, 16);
            }
            $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex);
        }

        return $IPBin;
    }

    /**
     * 检测是否是合法的IP地址
     *
     * @param string $ip   IP地址
     * @param string $type IP地址类型 (ipv4, ipv6)
     *
     * @return boolean
     */
    public function isValidIP(string $ip, string $type = ''): bool
    {
        switch (strtolower($type)) {
            case 'ipv4':
                $flag = FILTER_FLAG_IPV4;
                break;
            case 'ipv6':
                $flag = FILTER_FLAG_IPV6;
                break;
            default:
                $flag = 0;
                break;
        }

        return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag));
    }
}
